Conversation
There was a problem hiding this comment.
Pull request overview
This PR targets reduced memory pressure and improved cleanup by refining disposal patterns and reusing allocations in rendering/editor paths, plus replacing texture hashing with a Blake3-based approach.
Changes:
- Added/adjusted disposal patterns (including event unsubscription and finalizer suppression) to reduce leaks and redundant finalization.
- Reduced allocations in geometry clearing, ghost block drawing, and room sorting by reusing buffers/caches.
- Replaced texture hashing with Blake3 hashing of dimensions + pixel data.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| TombLib/TombLib/Wad/WadTexture.cs | Switches texture hashing to Blake3 and removes MemoryStream-based hashing. |
| TombLib/TombLib/LevelData/RoomGeometry.cs | Attempts to preserve list capacity across clears to reduce re-allocations. |
| TombLib/TombLib/LevelData/ImportedGeometry.cs | Makes imported geometry textures disposable and disposes GPU textures when not reused. |
| TombLib/TombLib/IO/BinaryWriterFast.cs | Suppresses finalization after disposal. |
| TombLib/TombLib.Rendering/Rendering/RenderingFont.cs | Fixes dispose guard logic and suppresses finalization. |
| TombEditor/Controls/Panel3D/Panel3DDrawCollector.cs | Reuses a cached distance buffer for sorting rooms to draw. |
| TombEditor/Controls/Panel3D/Panel3DDraw.cs | Reuses a cached vertex array for ghost block rendering. |
| TombEditor/Controls/Panel3D/Panel3D.cs | Adds fields backing the new draw/sort caches. |
| TombEditor/Controls/ImportedGeometryManager.cs | Stores/removes a ListChanged handler to avoid event-handler leaks on dispose. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
TombLib/TombLib.Rendering/Rendering/RenderingFont.cs:323
RenderingFont.Dispose()now runs (the_disposedguard was fixed) and it disposesTextureAllocator. However,TextureAllocatoris provided by the caller viaRenderingFont.Description, and several call sites dispose the allocator separately (often before disposing the font). This makes ownership unclear and can lead to redundant/double disposal or ordering issues. Consider removing allocator disposal fromRenderingFont(caller-owned), or update all call sites to treat the allocator as font-owned and dispose only once (and in a consistent order).
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
TextureAllocator?.Dispose();
GDI.DeleteObject(_gdiFont);
TombLib/TombLib.Rendering/Rendering/RenderingFont.cs:335
- The finalizer
~RenderingFont()callsDispose(). Now thatDispose()performs full managed cleanup (including disposingTextureAllocator) and callsGC.SuppressFinalize, the finalizer thread will run that managed disposal path too. Consider switching to the standardDispose(bool disposing)pattern so the finalizer only releases unmanaged resources (and does not touch potentially already-finalized managed objects), and onlyDispose(true)disposes managed fields.
GC.SuppressFinalize(this);
}
~RenderingFont()
{
Dispose();
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@copilot Could you please verify this PR is production ready and that there are no regressions because of the changes. |
…lReferenceException Agent-Logs-Url: https://github.com/TombEngine/Tomb-Editor/sessions/a17e74ac-eb6d-41c7-9862-469fbbc04719 Co-authored-by: Nickelony <20436882+Nickelony@users.noreply.github.com>
I've verified the PR is production-ready. Key findings: Code Analysis:
Fix Applied (cca21b8): Build Status: No Regressions:
|
| var roomsToDraw = CollectRoomsToDraw(_editor.SelectedRoom).ToArray(); | ||
| var roomsToDrawDistanceSquared = new float[roomsToDraw.Length]; | ||
|
|
||
| if (_roomsDistanceCache == null || _roomsDistanceCache.Length < roomsToDraw.Length) |
There was a problem hiding this comment.
If I understand correctly, even if now _roomsDistanceCache is not recreated every draw call, it can grow infinitely, depending on the overall room count, and is never cleared. For example, if I load a level with 700 rooms and turn on "Draw all rooms" mode, _roomsDistanceCache will now grow to 700 units and will stay like that, even if I will close the level and make a new one with just 1 room.
There was a problem hiding this comment.
You're right, I've pushed a fix for this.
|
|
||
| private ImportedGeometryTexture(ImportedGeometryTexture other) | ||
| { | ||
| DirectXTexture = other.DirectXTexture; |
There was a problem hiding this comment.
Is this assignment now happening elsewhere, or not needed?
There was a problem hiding this comment.
This is an ownership problem, since DirectXTexture needs to be disposed, you can't make 2 instances of ImportedGeometryTexture share the same reference. I remember checking this and found no issues with this being removed, but I will check again later.
There was a problem hiding this comment.
@copilot From my initial analysis, the clones of the textures are purely for data driven operations, and the DX texture itself doesn't need to be copied, but I please double-check this claim and whether the DX texture not being copied causes any new issues/regressions.
There was a problem hiding this comment.
Verified - removing DirectXTexture from the clone copy constructor is safe and does not cause regressions.
Analysis:
- The
Clone()method onImportedGeometryTextureis inherited from the abstractTextureclass - After exhaustive search,
ImportedGeometryTexture.Clone()is never called anywhere in the codebase - All explicit
texture.Clone()calls use(LevelTexture)texture.Clone()cast, targeting onlyLevelTextureobjects - The
LevelSettings.Textureslist only containsLevelTexture, notImportedGeometryTexture ImportedGeometryTextureis stored inImportedGeometry.Textureslist, andImportedGeometry.Clone()usesMemberwiseClonewhich copies the list reference, not the individual textures
Key points:
- Rendering paths (
Panel3DDraw,WadObjectRenderHelper) accessDirectXTexturedirectly viaMaterial.Texture- no cloning involved - Compilers access the texture's
Imageproperty for data operations -DirectXTextureis GPU-only - Even if
Clone()were called, the cloned texture would be data-only which is correct sinceDirectXTextureis uploaded lazily in the constructor
The change is correct and improves ownership semantics.
Addresses memory leaks and GC pressure from improper disposal patterns, missing event unsubscription, and redundant allocations in rendering paths.
Resource Management
Dispose()for no reasonReleaseUnmanagedResources()for proper destructor handling, addGC.SuppressFinalizeGC.SuppressFinalizeto skip redundant finalizer queueIDisposablewith_disposedguard to prevent GPU texture allocation after disposal in asyncSynchronizationContext.PostcallbackDisposeto preventNullReferenceExceptionin design-time or early disposalEvent Handler Cleanup
ListChangedhandler reference for explicit unsubscription during disposalRendering Optimizations
_ghostBlockVerticesarray and_roomsDistanceCacheto eliminate per-frame allocationsRenderingFont.Dispose()now ownsTextureAllocator, remove redundant_fontTexture?.Dispose()callsTexture Hashing
Replaced
BinaryWriter+MemoryStream+ToArray()with direct Blake3 hashing: